package com.example.log.alarm.advice.thread.pool;

import com.example.log.alarm.constant.ThrowableConstant;
import com.example.log.alarm.exception.ThreadPoolException;
import com.example.log.alarm.queue.ResizableCapacityLinkedBlockingQueue;
import com.example.log.alarm.util.ReflectUtil;
import com.example.log.alarm.util.WrapperUtil;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Collectors;

/**
 * @program: spring-agent
 * @description:
 * @author: zt22328 赵儒定
 * @create: 2023-05-11 09:50
 **/
public class ThreadPoolWrapper {
    /**
     * threadNamePrefix
     */
    private String threadPoolId;

    private ThreadPoolExecutor executor;

    private ThreadInfo threadInfo;

    private LocalDateTime createdAt;

    private String createdThreadName;

    private String stackTrace;

    private LongAdder errorCount = new LongAdder();

    private Map<String, LongAdder> rejectedCountMap = new ConcurrentHashMap<>();

    public ThreadPoolWrapper(Map<String, Object> map) {
        this.executor = (ThreadPoolExecutor) map.get(ThrowableConstant.THREAD_POOL);
        this.createdThreadName = (String) map.get(ThrowableConstant.THREAD_NAME);
        this.createdAt = (LocalDateTime) map.get(ThrowableConstant.CREATED_AT);
        this.stackTrace = WrapperUtil.getStackTrace((Throwable) map.get(ThrowableConstant.STACK_TRACE));
        this.threadInfo = WrapperUtil.getThreadInfo(this.executor.getThreadFactory());
        this.threadPoolId = this.threadInfo.getThreadNamePrefix();// + "-[" + this.getClassName() + "]";
    }

    public String getClassName() {
        return executor.getClass().getName();
    }

    public boolean isRunning() {
        return !executor.isShutdown();
    }

    public int getActiveCount() {
        return executor.getActiveCount();
    }

    public long getCompletedTaskCount() {
        return executor.getCompletedTaskCount();
    }

    public long getTaskCount() {
        return executor.getTaskCount();
    }

    public long getPoolSize() {
        return executor.getPoolSize();
    }

    public long getCorePoolSize() {
        return executor.getCorePoolSize();
    }

    public long getMaximumPoolSize() {
        return executor.getMaximumPoolSize();
    }

    public long getLargestPoolSize() {
        return executor.getLargestPoolSize();
    }

    public long getKeepAliveTime() {
        return executor.getKeepAliveTime(TimeUnit.SECONDS);
    }

    public String getRejectedExecutionHandlerClassName() {
        return executor.getRejectedExecutionHandler().getClass().getName();
    }

    public String getQueueClassName() {
        return executor.getQueue().getClass().getName();
    }

    public int getQueueUseSize() {
        return executor.getQueue().size();
    }

    public int getQueueMaxCapacity() {
        return executor.getQueue().remainingCapacity();
    }

    public int getQueueRemainingCapacity() {
        return executor.getQueue().remainingCapacity();
    }

    public void setCorePoolSize(int corePoolSize) {
        this.executor.setCorePoolSize(corePoolSize);
    }

    public void setKeepAliveTime(long keepAliveTime) {
        this.executor.setKeepAliveTime(keepAliveTime, TimeUnit.SECONDS);
    }

    public void setMaximumPoolSize(int maximumPoolSize) {
        this.executor.setMaximumPoolSize(maximumPoolSize);
    }

    public void setRejectedExecutionHandler(String className) {
        Object reject = ReflectUtil.newInstance(className);
        if (reject instanceof RejectedExecutionHandler) {
            throw new ThreadPoolException("RejectedExecutionHandler class wrong");
        }
        this.executor.setRejectedExecutionHandler((RejectedExecutionHandler) ReflectUtil.newInstance(className));
    }

    public void setQueueSize(Integer size) {
        if (!(executor.getQueue() instanceof ResizableCapacityLinkedBlockingQueue)) {
            throw new ThreadPoolException("The queue size cannot be modified");
        }
        ResizableCapacityLinkedBlockingQueue<Runnable> queue = (ResizableCapacityLinkedBlockingQueue<Runnable>) executor.getQueue();
        queue.setCapacity(size);
    }

    public String getThreadPoolId() {
        return threadPoolId;
    }

    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public String getCreatedThreadName() {
        return createdThreadName;
    }

    public String getStackTrace() {
        return stackTrace;
    }

    public ThreadInfo getThreadInfo() {
        return threadInfo;
    }

    public long getErrorCount() {
        return this.errorCount.longValue();
    }

    public ThreadPoolExecutor executor() {
        return executor;
    }

    public void incrementError() {
        this.errorCount.increment();
    }

    public long getRejectedCount() {
        return rejectedCountMap.values().stream().map(e->e.longValue()).collect(Collectors.summingLong(e->e));
    }

    public void incrementRejected(String rejectedStack) {
        LongAdder longAdder = rejectedCountMap.get(rejectedStack);
        if (longAdder == null) {
            synchronized (this) {
                longAdder = rejectedCountMap.get(rejectedStack);
                if (longAdder == null) {
                    longAdder = new LongAdder();
                    longAdder.increment();
                    rejectedCountMap.put(rejectedStack, longAdder);
                    return;
                }
            }
        }
        longAdder.increment();
    }

    public Map<String, Long> getRejectedCountMap() {
        Map<String, LongAdder> map = new HashMap<>(rejectedCountMap);
        Map<String, Long> mapCounts = new HashMap<>();
        map.forEach((k, v) -> {
            mapCounts.put(k, v.longValue());
        });
        return mapCounts;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null || !(o instanceof ThreadPoolWrapper)) {
            return false;
        }
        ThreadPoolWrapper that = (ThreadPoolWrapper) o;
        return this.threadPoolId.equals(that.threadPoolId);
    }

    @Override
    public int hashCode() {
        return this.threadPoolId.hashCode();
    }

}
